package own.stu.jobgib.playown.io.common;

import java.net.Socket;
import java.net.SocketException;

/**
 * https://blog.csdn.net/dong1528313271/article/details/83272675
 *
 */
public class SocketOptionTest {

    public static enum Type {
        FromClient(0), FromServer(1);
        private int code;

        Type(int code) {
            this.code = code;
        }

        public int getCode() {
            return code;
        }
    }

    protected static class Args {

        private Socket socket;

        public Args(Socket socket) {
            this.socket = socket;
        }

        /**
         * 缓冲的目的是可以充分利用网卡的发送能力。他会平滑的将你放入缓冲区的数据发出去。
         *
         * 当你有32k的缓冲，如果只发8k的话，他是会有一个延时，但不会很长，通常在200ms以内。
         *
         * 而当你有8k缓存，而发送32k数据的时候，他是会立即发送前8k数据的。
         */
        Args SO_SNDBUF(int size) throws SocketException {
            socket.setSendBufferSize(size);
            return this;
        }

        //https://plantegg.github.io/2019/09/28/%E5%B0%B1%E6%98%AF%E8%A6%81%E4%BD%A0%E6%87%82TCP--%E6%80%A7%E8%83%BD%E5%92%8C%E5%8F%91%E9%80%81%E6%8E%A5%E6%94%B6Buffer%E7%9A%84%E5%85%B3%E7%B3%BB/
        Args SO_RCVBUF(int size) throws SocketException {
            socket.setReceiveBufferSize(size);
            return this;
        }

        //SO_Keeplive是实现在服务器侧，客户端被动响应，缺省超时时间为120分钟，这是RFC协议标准规范。 https://www.zhihu.com/question/40602902
        Args SO_KEEPALIVE(boolean val) throws SocketException {
            socket.setKeepAlive(val);
            return this;
        }

        /**
         * SO_LINGER选项用来设置延迟关闭的时间，等待套接字发送缓冲区中的数据发送完成。没有设置该选项时，在调用close()后，在发送完FIN后会立即进行一些清理工作并返回。如果设置了SO_LINGER选项，并且等待时间为正值，则在清理之前会等待一段时间。
         * 以调用close()主动关闭为例，在发送完FIN包后，会进入FIN_WAIT_1状态。如果没有延迟关闭（即设置SO_LINGER选项），在调用tcp_send_fin()发送FIN后会立即调用sock_orphan()将sock结构从进程上下文中分离。分离后，用户层进程不会再接收到套接字的读写事件，也不知道套接字发送缓冲区中的数据是否被对端接收。如果设置了SO_LINGER选项，并且等待时间为大于0的值，会等待套接字的状态从FIN_WAIT_1迁移到FIN_WAIT_2状态。我们知道套接字进入FIN_WAIT_2状态是在发送的FIN包被确认后，而FIN包肯定是在发送缓冲区中的最后一个字节，所以FIN包的确认就表明发送缓冲区中的数据已经全部被接收。当然，如果等待超过SO_LINGER选项设置的时间后，还是没有收到FIN的确认，则继续进行正常的清理工作，Linux下也没有返回错误。从这里看来，SO_LINGER选项的作用是等待发送缓冲区中的数据发送完成，但是并不保证发送缓冲区中的数据一定被对端接收（对端宕机或线路问题），只是说会等待一段时间让这个过程完成。如果在等待的这段时间里接收到了带数据的包，还是会给对端发送RST包，并且会reset掉套接字，因为此时已经关闭了接收通道。
         * 在使用这个选项来延迟关闭连接的时候有两个地方需要注意：
         * 1. 进程会睡眠，直到状态不为FIN_WAIT_1、CLOSING、LAST_ACK（也就是接收到对FIN的ACK包），或者等待超时
         * 2. 在等待的过程中如果接收到带数据的包还是会发送RST包
         * 3.消耗更多的额外资源
         *   TCP协议是一个通用的传输层协议，不关心上层具体的业务，如果要延迟关闭连接，最好是结合自己的业务和场景自己来管理，不要依赖这个选项。nginx的延迟关闭就是自己来管理的，觉得要比直接使用SO_LINGER选项好一些，并且不会导致进程阻塞。 ngxin在发送错误信息后，会等待一段时间，让用户把所有的数据都发送完。超过等待时间后，会直接关闭连接。通过lingering_close，nginx可以保持更好的客户端兼容性，避免客户端被reset掉。
         *   SO_LINGER还有一个作用就是用来减少TIME_WAIT套接字的数量。在设置SO_LINGER选项时，指定等待时间为0，此时调用主动关闭时不会发送FIN来结束连接，而是直接将连接设置为CLOSE状态，清除套接字中的发送和接收缓冲区，直接对对端发送RST包。
         *
         *   https://blog.csdn.net/xiaofei0859/article/details/51138048
         *   https://blog.51cto.com/xwandrew/2046615
         */
        Args SO_LINGER(boolean val, int linger) throws SocketException {
            socket.setSoLinger(val, linger);
            return this;
        }

        // 从inputsteam中读取数据的超时时间，0表示一直等待
        Args SO_TIMEOUT(int timeout) throws SocketException {
            socket.setSoTimeout(timeout);
            return this;
        }

        //https://www.cnblogs.com/kex1n/p/7437290.html
        Args SO_REUSEADDR(boolean val) throws SocketException {
            socket.setReuseAddress(val);
            return this;
        }

        // 带外数据（Out-Of-Band data）
        Args SO_OOBINLINE(boolean val) throws SocketException {
            socket.setOOBInline(val);
            return this;
        }

        Args TCP_NODELAY(boolean val) throws SocketException {
            socket.setTcpNoDelay(val);
            return this;
        }

    }

    private int type;

    public static void built(Socket socket, int type) {
        if (type == Type.FromClient.getCode()) {
            try {
                new Args(socket).SO_SNDBUF(128);
            } catch (SocketException e) {
                e.printStackTrace();
            }
        }
        if(type == Type.FromServer.getCode()){
            try {
                new Args(socket).SO_RCVBUF(64);
            } catch (SocketException e) {
                e.printStackTrace();
            }
        }
    }
}
